package com.atguigu.juc.locks;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;

/**
 * @author yanglin
 * @create 2024-02-19 10:58 星期一
 * description:
 */
public class StampedLockDemo {

    /**
     * 无锁  -->  独占锁 --> 读写锁 -->  邮戳锁
     * 邮戳锁（StampedLock）：（JDK8中新增的一个读写锁，即对可重入读写锁的优化）
     *      stamp（戳记，long类型）：代表了锁的状态，当stamp返回0时，表示线程获取锁失败，并且，当释放锁或者转换锁的时候，
     *                              都要传入最初的stamp值
     *      锁饥饿问题：
     *          ReentrantReadWriteLock实现了读写分离，但是一旦读操作比较多时，想要获取写锁就变得比较困难，
     *          加入当前有1000个线程，999个读，1个写，有可能999个读取线程长时间抢到了锁，那一个写线程就很难
     *          获取到锁，因为当前有可能会一直存在读锁，而无法获取到写锁（读锁没有完成的时候，是无法获取到写锁的），
     *          根本没有机会写
     *          
     *     对于短的只读代码段，使用乐观模式通常可以减少争用并提高吞吐量
     *     
     *     特点：
     *          所有获取锁的方法，都返回一个邮戳（Stamp），Stamp为零表示获取失败，其余都表示成功
     *          所有获取锁的方法，都需要一个邮戳（Stamp），这个Stamp必须和成功获取锁时获得的Stamp一致，
     *          StampedLock是不可重入的，危险（如果一个线程已经持有了写锁，在获取写锁的话就会造成死锁）
     *          StampedLock有三种访问模式：
     *              1、Reading（读模式悲观）：功能和ReentrantReadWriteLock的读锁类似
     *              2、Writing（写模式）：功能和ReentrantReadWriteLock的写锁类似
     *              3、Optimistic reading（乐观读模式）：无锁机制，类似于数据库中的乐观锁，
     *                  支持读写并发，很乐观的认为读取时没人修改，假如被修改，在实现升级为悲观锁的读模式
     *    缺点：
     *          StampedLock不支持重入
     *          StampedLock悲观锁和写锁都不支持条件变量（Condition）
     *          使用StampedLock一定不要调用中断操作，即interrupt()方法
     *          
     *   总结：读的过程也允许写锁介入
     *      
     */

    /**
     * StampedLock = ReentrantReadWriteLock + 读的过程中也允许获取写锁介入
     */

    static int num = 37;
    static StampedLock stampedLock = new StampedLock();
    
    //写
    public void write(){
        long stamp = stampedLock.writeLock();
        System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备修改");
        try {
            num = num + 13;
        }finally {
            //解锁
            stampedLock.unlockWrite(stamp);
        }
        System.out.println(Thread.currentThread().getName()+"\t" + "写线程完成修改");
    }
    
    //悲观读：读没有完成的时候，写锁无法获得
    public void read(){
        long stamp = stampedLock.readLock();
        System.out.println(Thread.currentThread().getName() + "\t" + "come in readLock code block，4 seconds continue...");
        for (int i = 0; i < 4; i++) {
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "正在读取中。。。。。");
        }
        try {
            int result = num;
            System.out.println(Thread.currentThread().getName()+"\t" + " 获得成员变量值：" + result);
            System.out.println("写线程没有修改成功，读锁时候，写锁无法介入");
        }finally {
            stampedLock.unlockRead(stamp);
        }
    }
    
    //乐观读：读的过程中也允许获取写锁介入
    public void tryOptimisticRead(){
        long stamp = stampedLock.tryOptimisticRead();
        int result = num;
        //间隔4秒，很乐观认为读取中没有其他线程修改num的值，具体靠判断
        System.out.println("4秒前stampedLock.validate方法的值（true无修改，false有修改）" + stampedLock.validate(stamp));
        for (int i = 0; i < 4; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "\t" + "正在读取...." + i + "秒后" +
                    "stampedLock.validate方法的值（true无修改，false有修改） " + stampedLock.validate(stamp));
        }
        if (!stampedLock.validate(stamp)){
            System.out.println("有人修改过----有写操作");
            stamp = stampedLock.readLock();
        }
        try {
            System.out.println("从乐观读升级为悲观读");
            result = num;
            System.out.println("重新悲观读后result： " + result);
        }finally {
            stampedLock.unlockRead(stamp);
        }
        System.out.println(Thread.currentThread().getName() + "\t" + "finally value + " + result);
    }
    
    public static void main(String[] args) {
        StampedLockDemo resource = new StampedLockDemo();
        
        //读写锁演示
        /*new Thread(() -> {
            resource.read();
        },"readThread").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + " come in");
            resource.write();
        },"writeThread").start();*/
        
        new Thread(() -> {
            resource.tryOptimisticRead();  //乐观读
        },"readThread").start();

        
        //暂停2秒钟的时候，读过程可以写介入，演示
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + " come in");
            resource.write();
        },"writeThread").start();
    }

}
